package com.blackwatchplaid;

import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.*;

// Downloader starts a background thread to download a URL. A "listener" 
// callback will be notified of progress, error and completion.
// This class really has to be rethought. Need to tie this URL downloading
// concept in with loading things from an RMS cache, or maybe even a text
// message.
public class Downloader {
	
	public interface DownloadJobListener {
		void notifyProgress(String url, int bytesRead, int bytesRemaining, String progress );
		void notifyError(String url, int bytesRead, int bytesRemaining, String errorMessage );
	}
	public interface DownloadImageJobListener extends DownloadJobListener {
		void imageComplete(String url, Image image );
	}
	public interface DownloadTextJobListener extends DownloadJobListener {
		void textComplete(String url, String text );
	}

      	public interface DownloadStreamJobListener extends DownloadJobListener {
		void streamOpened(String url, InputStream in);
	}

	// Object put in cache when an error has occurred for a url
	public static class DownloadError {
		int bytesRead;
		int bytesRemaining;
		String errorMessage;
		public DownloadError(int bytesRead, int bytesRemaining, String errorMessage) {
			this.bytesRead = bytesRead;
			this.bytesRemaining = bytesRemaining;
			this.errorMessage = errorMessage;
		}
	}
	
	public static class LoggingInputStream extends InputStream {
		InputStream wrapped;
		LoggingInputStream(InputStream myWrapped) {
			System.out.println("LoggingInputStream: "+myWrapped);
			wrapped = myWrapped;
		}
		public int available() throws IOException {
			int i = wrapped.available();
			System.out.println("LoggingInputStream.available: "+i);
			return i;
		}

		public void close() throws IOException {
			System.out.println("LoggingInputStream.close");
			wrapped.close();
		}

		public synchronized void mark(int arg0) {
			System.out.println("LoggingInputStream.mark: "+arg0);
			wrapped.mark(arg0);
		}

		public boolean markSupported() {
			boolean b = wrapped.markSupported(); 
			System.out.println("LoggingInputStream.markSupported: "+b);
			return b;
		}

		public int read() throws IOException {
			int r = wrapped.read(); 
			System.out.println("LoggingInputStream.read: "+r);
			return r;
		}

		public int read(byte[] arg0, int arg1, int arg2) throws IOException {
			int r = wrapped.read(arg0, arg1, arg2); 
			System.out.println("LoggingInputStream.read("+new String(arg0,arg1,arg2,"UTF-8")+","+arg1+","+arg2+"): "+r);
			return r;
		}

		public int read(byte[] arg0) throws IOException {
			int r = wrapped.read(arg0); 
			System.out.println("LoggingInputStream.read("+new String(arg0)+"): "+r);
			return r;
		}

		public synchronized void reset() throws IOException {
			System.out.println("LoggingInputStream.reset");
			wrapped.reset();
		}

		public long skip(long arg0) throws IOException {
			long r = wrapped.skip(arg0); 
			System.out.println("LoggingInputStream.skip("+arg0+"): "+r);
			return r;
		}
		
	}

	public Downloader() {
	}

	private String authorizationHeaderValue;
	public Downloader(String userName, String passWord) {
		authorizationHeaderValue = "Basic " + Base64.encode((userName+":"+passWord).getBytes());
	}
	
	private class CacheData {
		Object val; // value being cached: Image or DownloadJob or Text
		int numAccess;
		CacheData(Object myVal) {
			val = myVal;
			numAccess = 0;
		}
	}
	// maps urls to CacheData
	final int maxCache = 0;
	Hashtable cache = new Hashtable(maxCache);
	
	// mra[0] has the least recently accessed URL
	String[] mra = new String[maxCache];
	
	private static volatile int guid = 0;

	abstract class DownloadJob extends Thread implements DownloadJobListener {
		protected String url;
		protected Vector listeners = new Vector(0);
		
		DownloadJob(String myURL) {
			super("DownloadJob-"+ guid++ +"-"+myURL);
			url = myURL; 
		}
		
		// No notifyComplete because it's not complete when the bytes are done
		
		public void notifyProgress(String url, int bytesRead, int bytesRemaining, String progress) {
			for (int i=0; listeners!=null&&i<listeners.size(); ++i)
				((DownloadJobListener) listeners.elementAt(i)).notifyProgress(url, bytesRead, bytesRemaining, progress);
		}

		// Must un-cache DownloadJob before notifying listeners
		public void notifyError(String url, int bytesRead, int bytesRemaining, String errorMessage) {
			System.out.println("DownloadJob.notifyError eror from job '"+url+"': "+errorMessage);
			synchronized (cache) {
				cache.remove(url);
				cache.put(url,new CacheData(new DownloadError(bytesRead,bytesRemaining,errorMessage)));
				purgeCache();
			}
			for (int i=0; listeners!=null&&i<listeners.size(); ++i)
				((DownloadJobListener) listeners.elementAt(i)).notifyError(url, bytesRead, bytesRemaining, errorMessage);
			listeners.setSize(0);
		}
		

		void popListener() {
			DownloadJobListener r;
			synchronized (cache) {
				r = (DownloadJobListener)listeners.lastElement();
				listeners.setSize(listeners.size()-1);
			}
		}
		void addListener(DownloadJobListener myListener) {
			synchronized (cache) {
				if (!listeners.contains(myListener))
					listeners.addElement(myListener);
			}
		}
		
		void removeListener(DownloadJobListener myListener) {
			synchronized (cache) {
				int i=0;
				while (i<listeners.size()) {
					if (listeners.elementAt(i)==myListener)
						listeners.removeElementAt(i);
					else
						++i;
				}
			}
		}

		// Convert the bytes read to the originally requested object,
		// cache it, and notify listeners.
		abstract protected void realizeData(byte[] raw);

		protected void purgeCache() {
                        if (maxCache==0)
                            return;
			synchronized (cache) {
			int numImagesCached=0;
			while (numImagesCached<mra.length && mra[numImagesCached]!=null) {
				++numImagesCached;
			}
			if (numImagesCached>=maxCache) {
				String urlToPurge = mra[0]; // least number of accesses
				for (int i=0; i<mra.length; ++i) {
					mra[i] = i<mra.length-1 ? mra[i+1] : null;
				}
				if (urlToPurge==null) {
					System.out.println("No URLs to purge, need to kill pending jobs. cache.size="+cache.size());
				} else {
					System.out.println("Purging url: "+urlToPurge);
					cache.remove(urlToPurge);
				}
			}
			int i=0;
			for (i=0; i<mra.length && mra[i]!=null; ++i)
				;
			if (i==mra.length) {
				System.out.println("ERROR: i==mra.length. numImagesCached="+numImagesCached);
			}
			mra[i] = url;
			}
		}
		

		public void run() {
			byte[] raw = null;
			try {
				raw = getData(url, this);
				if (raw==null || raw.length==0) {
					System.out.println("DownloadJob.run: ERROR for url: "+url);
					// notify listeners that it failed?
//					if (listeners.size()==0) {
//						System.out.println("No listeners to notify of failure for url: "+url);
//					}
//					for (int i=0; i<listeners.size(); ++i) {
//						System.out.println("Notifying listener '"+listeners.elementAt(i)+"' of failure for url: "+url);
//						((DownloadImageJobListener)listeners.elementAt(i)).imageComplete(url, null);
//					}
					return ;
				} else {
					realizeData(raw);
				}
			} catch (Throwable e) {
				System.out.println("ERROR: unexpected exception in run(), getData got '"+(raw==null?-1:raw.length)+"' for url: "+url+". Exception: "+e);
				e.printStackTrace();
				return;
			} finally {
			}
		}
	}
	class DownloadImageJob extends DownloadJob {
		DownloadImageJob(String url) {
			super(url);
		}
		protected void realizeData(byte[] raw) {
			try {
				Image image = Image.createImage(raw,0,raw.length);
				System.out.println("Caching image="+image);
				synchronized (cache) {
					// replace existing job with this image, so should not be
					// able to get any more listeners added to this object.
					cache.put(url, new CacheData(image));
					purgeCache();
				}
				// safe outside of synchronized, nobody else should be adding and this
				// allows more freedom for imageComplete()
				if (listeners.size()==0) {
					System.out.println("No listeners to notify for cached image "+image+" url: "+url);
				}
				for (int li=0; li<listeners.size(); ++li) {
					System.out.println("Notifying listener '"+listeners.elementAt(li)+"' for cached image "+image+" url: "+url);
					((DownloadImageJobListener)listeners.elementAt(li)).imageComplete(url, image);
				}
			} catch (java.lang.IllegalArgumentException iae) {
				System.out.println("ERROR: DownloadImageJob could not create image, got len='"+raw.length+"' for url: "+url+". Exception: "+iae);
				iae.printStackTrace();
				notifyError(url, -1, -1, "Could not create image, phone probably does not support JPEG. raw.length='"+raw.length+"' url='"+url+"': "+iae.getMessage());
			} catch (Throwable e) {
				System.out.println("ERROR: DownloadImageJob could not create image, got len='"+raw.length+"' for url: "+url+". Exception: "+e);
				e.printStackTrace();
				notifyError(url, -1, -1, "Could not create image. raw.length='"+raw.length+"' url='"+url+"': "+e.getMessage());
			}
		}
	}
	
	class DownloadTextJob extends DownloadJob {
		DownloadTextJob(String url) {
			super(url);
		}
		protected void realizeData(byte[] raw) {
			try {
				String image;
				try {
					//!!!image = new String(raw, "UTF-8");
        				//!!!System.out.println("Converted raw.length bytes "+raw.length+" to string.length="+image.length());
					image = new String(raw, "ISO-8859-1");
        				System.out.println("!!!TryISO: Converted raw.length bytes "+raw.length+" to string.length="+image.length());
				} catch (Exception e) {
        				System.out.println("ERROR: DownloadTextJob failed to convert raw.length="+raw.length+" to UTF-8: "+e);
                                        e.printStackTrace();
					image = new String(raw, "ISO-8859-1");
				}
				System.out.println("Caching text("+image.length()+")="+image.substring(0,5)+"...");
				synchronized (cache) {
					cache.put(url, new CacheData(image));
					purgeCache();
				}
				if (listeners.size()==0) {
					System.out.println("No listeners to notify for cached image "+image+" url: "+url);
				}
				for (int li=0; li<listeners.size(); ++li) {
					System.out.println("Notifying listener '"+listeners.elementAt(li)+"' for cached text ("+image.length()+") url: "+url);
                                        try {
                                            ((DownloadTextJobListener)listeners.elementAt(li)).textComplete(url, image);
                                        } catch (Exception e) {
                                            System.out.println("ERROR: DownloadTextJob error while notifying listener '"+li+"."+listeners.elementAt(li)+"' for url: "+url+". Exception: "+e);
                                            e.printStackTrace();
                                            
                                        }
				}
			} catch (Throwable e) {
				System.out.println("ERROR: DownloadTextJob.realizeData failed, got '"+raw.length+"' for url: "+url+". Exception: "+e);
				e.printStackTrace();
			}
		}
	}

	class DownloadStreamJob extends DownloadJob {
            DownloadStreamJob(String url) {
                super(url);
            }
            protected void realizeData(byte[] raw) {
                throw new IllegalArgumentException("DownloadStreamJob.realizeData should not be called");
            }
            public void run() {
                HttpConnection con = null;
                try {
                    String cookies = "ASP.NET_SessionId=lkdciseumzeurcu2xifnibim";
                    tryagain: do {
                        long startTime = System.currentTimeMillis();
                        con = (HttpConnection)Connector.open(url);
                        //!!!InputStream is = null;
			//!!!byte[] raw = null;
                        try {
                            if (cookies!=null)
                                con.setRequestProperty("Cookie",cookies);
                            if (authorizationHeaderValue!=null)
                                con.setRequestProperty("Authorization", authorizationHeaderValue);
                            int contentLength = (int)con.getLength();
                            System.out.println("DEBUG: DownloadStreamJob.run contentLength="+contentLength);
                            for (int i=0; con.getHeaderField(i)!=null; ++i) {
                                System.out.println("DEBUG: DownloadStreamJob.run: "+con.getHeaderFieldKey(i)+": "+con.getHeaderField(i)+".");
                            }
                            if (contentLength==-1) {
                                System.out.println("ERROR: DownloadStreamJob.run con.getLength() returned -1");
                                contentLength = 20000; //!!!! maximum download size?
                            }
                            //is = new LoggingInputStream(con.openInputStream());
                            //!!!is = con.openInputStream();
                            /*!!!
                            raw = new byte[contentLength];
                            int lastRead;
                            int length = contentLength;
                            while (length>0 && (lastRead=is.read(raw, contentLength-length, length)) != -1) {
                                length -= lastRead;
                                if (listener!=null) //!!! do not notify unless this is 200
                                    listener.notifyProgress(url, contentLength-length, contentLength, null);
                            }
                            System.out.println("DEBUG: Downloader.getData length="+length+" raw.length="+raw.length);
                            for (int j=0; j<raw.length; ++j) {
                                if ((raw[j]<0x20 && raw[j]!=9 && raw[j]!=13 && raw[j]!=10) || raw[j]>127)
                                    System.out.println("ERROR: Downloader.getData found unexpected char: "+j+"="+(int)raw[j]);
                            }
                            !!! */
                            if (con.getResponseCode()>=200 && con.getResponseCode()<300) {
                                System.out.println("DownloadStreamJob.run: Successfully made connection in '"+(System.currentTimeMillis()-startTime)/100+"' secs, statusCode='"+con.getResponseCode()+"', responseMessage='"+con.getResponseMessage()+"', url: "+url);
                                
                                InputStream is=null;
                                try {
                                    is = con.openInputStream();
                                    for (int li=0; li<listeners.size(); ++li) {
                                        System.out.println("DownloadStreamJob.run: Notifying listener '"+listeners.elementAt(li)+"' for stream of url: "+url);
                                        try {
                                            ((DownloadStreamJobListener)listeners.elementAt(li)).streamOpened(url, is);
                                        } catch (Exception e) {
                                            System.out.println("ERROR: DownloadStreamJob error while notifying listener '"+li+"."+listeners.elementAt(li)+"' for url: "+url+". Exception: "+e);
                                            e.printStackTrace();
                                        }
                                    }
                                } finally {
                                    if (is!=null)
                                        is.close();
                                }
                                
                            } else if (con.getResponseCode()>=301 && con.getResponseCode()<303) {
                                //!!!cookies = con.getHeaderField("set-cookie");
                                System.out.println("DownloadStreamJob.run: Got redirect in '"+(System.currentTimeMillis()-startTime)/100+"' secs, statusCode='"+con.getResponseCode()+"', responseMessage='"+con.getResponseMessage()+"', url: "+url);
                                System.out.println("DownloadStreamJob.run: Location: "+con.getHeaderField("Location")+". Set-Cookie: "+cookies);
                                
                                url = con.getHeaderField("Location");
                                if (!url.startsWith("http")) {
                                    url = "http://aclfestival.com"+url;
                                }
                                //!!!if (cookies!=null)
                                //!!!    cookies = cookies.substring(cookies.indexOf(':')+1,cookies.lastIndexOf(';'));
                                continue tryagain;
                            } else  {
                                System.out.println("DownloadStreamJob.run: unexpected response in '"+(System.currentTimeMillis()-startTime)/100+"' secs, statusCode='"+con.getResponseCode()+"', responseMessage='"+con.getResponseMessage()+"', url: "+url);
				for (int li=0; li<listeners.size(); ++li) {
					System.out.println("DownloadStreamJob.run: Notifying listener '"+listeners.elementAt(li)+"' of error on url: "+url);
                                        try {
                                            ((DownloadStreamJobListener)listeners.elementAt(li)).notifyError(url, 0, 0, "Unexpected response '"+con.getResponseCode()+" "+con.getResponseMessage()+"' for url: "+url);
                                        } catch (Exception e) {
                                            System.out.println("ERROR: DownloadStreamJob.run: error while notifying listener '"+li+"."+listeners.elementAt(li)+"' for url: "+url+". Exception: "+e);
                                            e.printStackTrace();
                                        }
				}
                                
                                //!!!if (listener!=null)
                                //!!!    listener.notifyError(url, contentLength-length, contentLength, "HTTP error: statusCode='"+con.getResponseCode()+"', responseMessage='"+con.getResponseMessage()+"', url: "+url);
                                //!!!raw = null;
                            }
                        } finally {
                            //!!!if (is!=null)
                             //!!!   is.close();
                            if (con!=null)
                                con.close();
                        }
                        //!!!return raw;
                        return;
                    }while (true);
                } catch (java.lang.SecurityException se) {
                    System.err.println("Download.getData: security exception, you probably didn't authorize to use Internet: " + se + " - " + se.getMessage());
                    se.printStackTrace();
                    //if (listener!=null)
                    //    listener.notifyError(url, -1, -1, "You did not authorize Internet connections for this app. '"+se.getMessage()+"', failed: "+url);
                    //!!!return null;
				for (int li=0; li<listeners.size(); ++li) {
					System.out.println("DownloadStreamJob.run: Notifying listener '"+listeners.elementAt(li)+"' of error on url: "+url);
                                        try {
                                            ((DownloadStreamJobListener)listeners.elementAt(li)).notifyError(url, 0, 0, "You did not authorize Internet connections for this app. '"+se.getMessage()+"', failed: "+url);
                                        } catch (Exception e) {
                                            System.out.println("ERROR: DownloadStreamJob.run: error while notifying listener '"+li+"."+listeners.elementAt(li)+"' of SecurityException for url: "+url+". Exception: "+e);
                                            e.printStackTrace();
                                        }
				}
                } catch (Throwable e) {
                    System.err.println("DownloadStreamJob.run: unexpected exception: " + e + " - " + e.getMessage());
                    e.printStackTrace();
                    int respCode=-1;
                    String respMessage=null;
                    try {
                        respCode = con.getResponseCode();
                        respMessage =con.getResponseMessage();
                        //if (listener!=null)
                        //    listener.notifyError(url, -1, -1, "HTTP error: statusCode='"+respCode+"', responseMessage='"+respMessage+"', url: "+url);
				for (int li=0; li<listeners.size(); ++li) {
					System.out.println("DownloadStreamJob.run: Notifying listener '"+listeners.elementAt(li)+"' of error on url: "+url);
                                        try {
                                            ((DownloadStreamJobListener)listeners.elementAt(li)).notifyError(url, 0, 0, "HTTP error: statusCode='"+respCode+"', responseMessage='"+respMessage+"', url: "+url);
                                        } catch (Exception ee) {
                                            System.out.println("ERROR: DownloadStreamJob.run: error while notifying listener '"+li+"."+listeners.elementAt(li)+"' of HTTP error '"+respCode+" "+respMessage+"' for url: "+url+". Exception: "+ee);
                                            ee.printStackTrace();
                                        }
				}
                    } catch (Throwable ee) {
                        System.err.println("Download.getData: super unexpected exception: " + ee + " - " + ee.getMessage());
                        ee.printStackTrace();
                        //!!!if (listener!=null)
                        //!!!    listener.notifyError(url, -1, -1, "HTTP error: statusCode='"+-1+"', exception='"+e.getMessage()+"', url: "+url);
                    }
                    //!!!return null;
                }
            }
        }
        
	
	// assumes you're in a synchronized (cache)
	private void startJob(String url, DownloadJob job, DownloadJobListener listener) {
		System.out.println("Starting job '"+job.getName()+"' to get url: "+url);
		cache.put(url, new CacheData(job));
		job.addListener(listener);
		job.start();
	}
	
	private void moveMRA(String url) {
		int i;
		for (i=0; i<mra.length && mra[i].intern()!=url.intern() && mra[i]!=null; ++i)
			;
		// i is at end of the array (this url not already listed) or i is at the url
		if (i==mra.length) { // error condition?
			System.out.println("ERROR: i==mra.length (getImage)");
		} else if (mra[i]==null){ // first time added
			mra[i] = url;
		} else { // move this element just accessed to end of the array
			while (i<mra.length-1 && mra[i+1]!=null) {
				mra[i] = mra[i+1];
				++i;
			}
			mra[i] = url;
		}
	}
	
	public Image getImage(String url, DownloadImageJobListener listener) {
		DownloadError err = null;
		synchronized (cache) {
			Object image = cache.get(url);
			if (image==null){
				startJob(url, new DownloadImageJob(url), listener);
			} else {
				image = ((CacheData)image).val;
				if (image instanceof Image) {
					System.out.println("Returning cached image "+image+" for url: "+url);
					moveMRA(url);
					return (Image)image;
				} else if (image instanceof DownloadError) {
					System.out.println("Returning error "+image+" for url: "+url);
					moveMRA(url);
					err = (DownloadError)image;
				} else if (image instanceof DownloadJob){
					System.out.println("Adding listener "+listener+" for url: "+url);
					((DownloadJob)image).addListener(listener);
				} else {
					System.out.println("ERROR: unexpected image '"+image+"' in cache for url: "+url);
				}
			}
		}
		if (err!=null) {
			listener.notifyError(url,err.bytesRead,err.bytesRemaining,err.errorMessage);
		}
		return null;
	}
	
	public String getText(String url, DownloadTextJobListener listener) {
		DownloadError err = null;
		synchronized (cache) {
			Object image = cache.get(url);
			if (image==null){
				startJob(url, new DownloadTextJob(url), listener);
			} else {
				image = ((CacheData)image).val;
				if (image instanceof InputStream) {
					System.out.println("Returning cached text "+image+" for url: "+url);
					moveMRA(url);
					return (String)image;
				} else if (image instanceof DownloadError) {
					System.out.println("Returning error "+image+" for url: "+url);
					moveMRA(url);
					err = (DownloadError)image;
					listener.notifyError(url,err.bytesRead,err.bytesRemaining,err.errorMessage);
				} else if (image instanceof DownloadJob){
					System.out.println("Adding listener "+listener+" for url: "+url);
					((DownloadJob)image).addListener(listener);
				} else {
					System.out.println("ERROR: unexpected image '"+image+"' in cache for url: "+url);
				}
			}
		}
		if (err!=null) {
			listener.notifyError(url,err.bytesRead,err.bytesRemaining,err.errorMessage);
		}
		return null;
	}
	
	public void getStream(String url, DownloadStreamJobListener listener) {
            startJob(url, new DownloadStreamJob(url), listener);
	}
	
	public byte[] getData(String url, DownloadJobListener listener ) {
		HttpConnection con = null;
		try {
                    String cookies = "ASP.NET_SessionId=lkdciseumzeurcu2xifnibim";
                    tryagain: do {
			long startTime = System.currentTimeMillis();
			con = (HttpConnection)Connector.open(url);
			InputStream is = null;
			byte[] raw = null;
			try {
                                if (cookies!=null)
                                    con.setRequestProperty("Cookie",cookies);
				if (authorizationHeaderValue!=null)
					con.setRequestProperty("Authorization", authorizationHeaderValue);
				int contentLength = (int)con.getLength();
                                System.out.println("DEBUG: Downloader.getData contentLength="+contentLength);
                                        for (int i=0; con.getHeaderField(i)!=null; ++i) {
                                            System.out.println("Downloader.getData: "+con.getHeaderFieldKey(i)+": "+con.getHeaderField(i)+".");
                                        }
                                        
				if (contentLength==-1)
					contentLength = 20000; //!!!! maximum download size?
				//is = new LoggingInputStream(con.openInputStream());
                                is = con.openInputStream();
				raw = new byte[contentLength];
				int lastRead;
				int length = contentLength;
				while (length>0 && (lastRead=is.read(raw, contentLength-length, /*length>1024 ? 1024:*/length)) != -1) {
					length -= lastRead;
					if (listener!=null) //!!! do not notify unless this is 200
						listener.notifyProgress(url, contentLength-length, contentLength, null);
				}
                                System.out.println("DEBUG: Downloader.getData length="+length+" raw.length="+raw.length);
                                for (int j=0; j<raw.length; ++j) {
                                    if ((raw[j]<0x20 && raw[j]!=9 && raw[j]!=13 && raw[j]!=10) || raw[j]>127)
                                        System.out.println("ERROR: Downloader.getData found unexpected char: "+j+"="+(int)raw[j]);
                                }
				if (con.getResponseCode()>=200 && con.getResponseCode()<300) {
					System.out.println("Downloader.getData: Successfully read content in '"+(System.currentTimeMillis()-startTime)/100+"' secs, length='"+length+"', contentLength='"+contentLength+"', raw="+raw+", statusCode='"+con.getResponseCode()+"', responseMessage='"+con.getResponseMessage()+"', url: "+url);
					if (listener!=null)
						listener.notifyProgress(url, contentLength, contentLength, null);
				} else if (con.getResponseCode()>=301 && con.getResponseCode()<303) {
                                        //!!!cookies = con.getHeaderField("set-cookie");
                                        System.out.println("Downloader.getData: Got redirect in '"+(System.currentTimeMillis()-startTime)/100+"' secs, length='"+length+"', contentLength='"+contentLength+"', raw="+raw+", statusCode='"+con.getResponseCode()+"', responseMessage='"+con.getResponseMessage()+"', url: "+url);
                                        System.out.println("Downloader.getData: Location: "+con.getHeaderField("Location")+". Set-Cookie: "+cookies);
                                        
                                        url = con.getHeaderField("Location");
                                        if (!url.startsWith("http")) {
                                            url = "http://aclfestival.com"+url;
                                        }
                                        //!!!if (cookies!=null)
                                        //!!!    cookies = cookies.substring(cookies.indexOf(':')+1,cookies.lastIndexOf(';'));
                                        continue tryagain;
                                } else  {
					System.out.println("Downloader.getData: Error reading content in '"+(System.currentTimeMillis()-startTime)/100+"' secs, length='"+length+"', contentLength='"+contentLength+"', raw="+raw+", statusCode='"+con.getResponseCode()+"', responseMessage='"+con.getResponseMessage()+"', url: "+url);
					if (listener!=null)
						listener.notifyError(url, contentLength-length, contentLength, "HTTP error: statusCode='"+con.getResponseCode()+"', responseMessage='"+con.getResponseMessage()+"', url: "+url);
					raw = null;
				}
			} finally {
				if (is!=null)
					is.close();
				if (con!=null)
					con.close();
			}
			return raw;
                    }while (true);
		} catch (java.lang.SecurityException se) {
			System.err.println("Download.getData: security exception, you probably didn't authorize to use Internet: " + se + " - " + se.getMessage());
			se.printStackTrace();
			if (listener!=null)
				listener.notifyError(url, -1, -1, "You did not authorize Internet connections for this app. '"+se.getMessage()+"', failed: "+url);
			return null;			
		} catch (Throwable e) {
			System.err.println("Download.getData: unexpected exception: " + e + " - " + e.getMessage());
			e.printStackTrace();
			int respCode=-1;
			String respMessage=null;
			try {
				respCode = con.getResponseCode();
				respMessage =con.getResponseMessage();
				if (listener!=null)
					listener.notifyError(url, -1, -1, "HTTP error: statusCode='"+respCode+"', responseMessage='"+respMessage+"', url: "+url);
			} catch (Throwable ee) {
				System.err.println("Download.getData: super unexpected exception: " + ee + " - " + ee.getMessage());
				ee.printStackTrace();
				if (listener!=null)
					listener.notifyError(url, -1, -1, "HTTP error: statusCode='"+-1+"', exception='"+e.getMessage()+"', url: "+url);
			}
			return null;
		}
	}
	
	public InputStream getInputStream(String url) {
		try {
			long startTime = System.currentTimeMillis();
			HttpConnection con = (HttpConnection)Connector.open(url);
			InputStream is = null;
			if (authorizationHeaderValue!=null)
				con.setRequestProperty("Authorization", authorizationHeaderValue);
			is = con.openInputStream();
			System.out.println("Finished getting InputStream for url: "+url);
			return new LoggingInputStream(is);
		} catch (Throwable e) {
			System.err.println("Hello: unexpected exception: " + e + " - " + e.getMessage());
			e.printStackTrace();
			return null;
		}
	}
	
}

